﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.Xml;

namespace eVision.SharpSort
{
    [XmlRoot("Region")]
    public class RegionDefinition : IComparer<CodeMemberInfo>
    {
        public enum RegionType { None, Comment, Region }

        [XmlAttribute]
        public int PaddingBefore { get; set; }
        [XmlAttribute]
        public int PaddingAfter { get; set; }
        [XmlAttribute]
        public int PaddingBetween { get; set; }

        [XmlAttribute]
        public String Name { get; set; }

        [XmlAttribute]
        public RegionType Type { get; set; }

        [XmlIgnore]
        public RegionDefinition[] ChildRegions { get; set; }

        [XmlIgnore]
        public Where Where { get; set; }

        [XmlArray("OrderBy")]
        [XmlArrayItem("Parameter", typeof(OrderParameter))]
        public OrderParameter[] OrderByParameters { get; set; }

        public IEnumerable<CodeMemberInfo> Sort(IEnumerable<CodeMemberInfo> members)
        {
            if (this.ChildRegions != null && this.ChildRegions.Length > 0)
            {
                throw new InvalidOperationException("Can't sort members in region with subregions.");
            }

            List<CodeMemberInfo> elements = new List<CodeMemberInfo>(members);
            elements.Sort(this);
            return elements;
        }

        [XmlAnyElement]
        public XmlElement[] UnknownElements
        {
            get
            {
                return null;
            }
            set
            {
                XmlElement[] elements = value;
                List<RegionDefinition> regions = new List<RegionDefinition>();
                List<Where> wheres = new List<Where>();

                if (elements != null)
                {
                    foreach (XmlElement element in elements)
                    {
                        Type target = ParseInformation.TypeByTagName[element.Name];

                        if (target == typeof(RegionDefinition))
                        {
                            RegionDefinition region = Serializer.XmlDeserialize<RegionDefinition>(element.OuterXml);
                            regions.Add(region);
                        }
                        else if (target == typeof(Where))
                        {
                            Where where = new Where(element);
                            wheres.Add(where);
                        }
                        else
                            throw new ArgumentOutOfRangeException(elements[0].OuterXml);
                    }
                }

                if (regions.Count > 0)
                {
                    if (wheres.Count > 0 || OrderByParameters != null)
                        throw new ArgumentOutOfRangeException("Region can't have defined query and sub regions.");
                }
                else
                {
                    if (wheres.Count > 1)
                        throw new ArgumentOutOfRangeException("Too many 'Wheres'.");
                    else if (wheres.Count == 1)
                        Where = wheres[0];
                    else
                        Where = new Where();
                }

                ChildRegions = regions.ToArray();
            }
        }

        #region IComparer<CodeMemberInfo> Members

        int IComparer<CodeMemberInfo>.Compare(CodeMemberInfo x, CodeMemberInfo y)
        {
            if (OrderByParameters != null)
            {

                foreach (OrderParameter parInfo in OrderByParameters)
                {
                    int result = 0;
                    switch (parInfo.Part)
                    {
                        case OrderParameter.PartEnum.Invalid:
                            throw new ArgumentException("Invalid sort comparison.");
                        case OrderParameter.PartEnum.AccessModifier:
                            result = x.AccessModifier - y.AccessModifier;
                            break;
                        case OrderParameter.PartEnum.EntityType:
                            result = x.EntityType - y.EntityType;
                            break;
                        case OrderParameter.PartEnum.Name:
                            result = string.CompareOrdinal(x.Name, y.Name);
                            break;
                        case OrderParameter.PartEnum.IsStatic:
                            result = (x.IsStatic ? 0 : 1) - (y.IsStatic ? 0 : 1);
                            break;
                        case OrderParameter.PartEnum.EntityVariableType:
                            result = string.CompareOrdinal(x.EntityVariableType, y.EntityVariableType);
                            break;
                        case OrderParameter.PartEnum.NumberOfParameters:
                            result = x.NumberOfParameters - y.NumberOfParameters;
                            break;
                        default:
                            break;
                    }

                    if (result != 0)
                    {
                        if (parInfo.Direction == OrderParameter.DirectionType.Descending)
                            result = -result;

                        return result;
                    }
                }

                return 0;
            }
            else
            {
                return string.CompareOrdinal(x.Name, y.Name);
            }
        }

        #endregion

        public bool HasSubRegions { get { return ChildRegions.Length > 0; } }

        private static void GenerateSequenceOfSymbols(StringBuilder sb, int number, String character)
        {
            for (int i = 0; i < number; ++i)
            {
                sb.Append(character);
            }
        }

        private bool IsOrderByType
        {
            get
            {
                return OrderByParameters != null && OrderByParameters.Length > 0 && OrderByParameters[0].Part == OrderParameter.PartEnum.EntityVariableType;
            }
        }

        public String GenerateCode(int depth, ICollection<CodeMemberInfo> membersToProcess, ICollection<CodeMemberInfo> removedMembers, out bool isEmpty)
        {
            StringBuilder sb = new StringBuilder();

            ICollection<CodeMemberInfo> membersThatSatisfy = null;
            List<String> childCodes = null;

            isEmpty = true;

            if (HasSubRegions)
            {
                childCodes = new List<string>();

                bool isChildEmpty = true;
                foreach (RegionDefinition childRegDef in ChildRegions)
                {
                    childCodes.Add(childRegDef.GenerateCode(depth, membersToProcess, removedMembers, out isChildEmpty));

                    if (!isChildEmpty)
                        isEmpty = false;
                }
            }
            else
            {
                membersThatSatisfy = new List<CodeMemberInfo>();
                foreach (CodeMemberInfo member in membersToProcess)
                {
                    if (Where.Evaluate(member))
                        membersThatSatisfy.Add(member);
                }

                foreach (CodeMemberInfo member in membersThatSatisfy)
                {
                    membersToProcess.Remove(member);
                    removedMembers.Add(member);
                }

                membersThatSatisfy = new List<CodeMemberInfo>(Sort(membersThatSatisfy));
                isEmpty = membersThatSatisfy.Count == 0;
            }

            if (isEmpty) return string.Empty;

            switch (this.Type)
            {
                case RegionType.None:
                    break;
                case RegionType.Comment:
                    GenerateSequenceOfSymbols(sb, depth, "\t");
                    sb.Append("//").Append((char)0xA0);
                    break;
                case RegionType.Region:
                    GenerateSequenceOfSymbols(sb, depth, "\t");
                    sb.Append("#region ");
                    break;
                default:
                    break;
            }

            if (Type != RegionType.None)
            {
                if (!string.IsNullOrEmpty(Name))
                    sb.Append(Name);

                if (!HasSubRegions)
                    sb.Append(" (").Append(membersThatSatisfy.Count).Append(")");
            }

            GenerateSequenceOfSymbols(sb, PaddingBefore, CodeFormatter.nl);

            bool first = true;
            if (!HasSubRegions)
            {
                CodeMemberInfo lastMember = null;
                foreach (CodeMemberInfo member in membersThatSatisfy)
                {
                    if (!member.Render) continue;

                    if (!first)
                        GenerateSequenceOfSymbols(sb, PaddingBetween, CodeFormatter.nl);

                    if (!first || (first && this.Type != RegionType.None))
                        sb.Append(CodeFormatter.nl);

                    if (IsOrderByType && lastMember != null && lastMember.EntityVariableType != member.EntityVariableType)
                        sb.Append(CodeFormatter.nl);

                    GenerateSequenceOfSymbols(sb, depth, "\t");
                    sb.Append(member.Code);

                    first = false;

                    lastMember = member;
                }
            }
            else
            {
                foreach (String code in childCodes)
                {
                    if (code == string.Empty) continue;

                    if (!first)
                    {
                        GenerateSequenceOfSymbols(sb, PaddingBetween, CodeFormatter.nl);
                    }

                    // ako smo napisali zaglavlje ili ako nije prvi
                    if (!first || (first && this.Type != RegionType.None))
                        sb.Append(CodeFormatter.nl);

                    sb.Append(code);
                    first = false;
                }
            }

            GenerateSequenceOfSymbols(sb, PaddingAfter, CodeFormatter.nl);

            switch (this.Type)
            {
                case RegionType.None:
                    break;
                case RegionType.Comment:
                    break;
                case RegionType.Region:
                    sb.Append(CodeFormatter.nl);
                    GenerateSequenceOfSymbols(sb, depth, "\t");
                    sb.Append("#endregion");
                    break;
                default:
                    break;
            }

            return sb.ToString();
        }
    }

}
